{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Copyright 2019 The Kubeflow Authors. All Rights Reserved.\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already up-to-date: kfp in /opt/conda/lib/python3.6/site-packages (0.1.30)\n",
      "Requirement already satisfied, skipping upgrade: kfp-server-api<=0.1.25,>=0.1.18 in /opt/conda/lib/python3.6/site-packages (from kfp) (0.1.18.3)\n",
      "Requirement already satisfied, skipping upgrade: tabulate==0.8.3 in /opt/conda/lib/python3.6/site-packages (from kfp) (0.8.3)\n",
      "Requirement already satisfied, skipping upgrade: cloudpickle in /opt/conda/lib/python3.6/site-packages (from kfp) (0.8.1)\n",
      "Requirement already satisfied, skipping upgrade: six>=1.10 in /opt/conda/lib/python3.6/site-packages (from kfp) (1.12.0)\n",
      "Requirement already satisfied, skipping upgrade: cryptography>=2.4.2 in /opt/conda/lib/python3.6/site-packages (from kfp) (2.6.1)\n",
      "Requirement already satisfied, skipping upgrade: PyYAML in /opt/conda/lib/python3.6/site-packages (from kfp) (3.13)\n",
      "Requirement already satisfied, skipping upgrade: python-dateutil in /opt/conda/lib/python3.6/site-packages (from kfp) (2.8.0)\n",
      "Requirement already satisfied, skipping upgrade: google-auth>=1.6.1 in /opt/conda/lib/python3.6/site-packages (from kfp) (1.6.3)\n",
      "Requirement already satisfied, skipping upgrade: PyJWT>=1.6.4 in /opt/conda/lib/python3.6/site-packages (from kfp) (1.7.1)\n",
      "Requirement already satisfied, skipping upgrade: requests-toolbelt>=0.8.0 in /opt/conda/lib/python3.6/site-packages (from kfp) (0.9.1)\n",
      "Requirement already satisfied, skipping upgrade: argo-models==2.2.1a in /opt/conda/lib/python3.6/site-packages (from kfp) (2.2.1a0)\n",
      "Requirement already satisfied, skipping upgrade: Deprecated in /opt/conda/lib/python3.6/site-packages (from kfp) (1.2.6)\n",
      "Requirement already satisfied, skipping upgrade: kubernetes<=9.0.0,>=8.0.0 in /opt/conda/lib/python3.6/site-packages (from kfp) (9.0.0)\n",
      "Requirement already satisfied, skipping upgrade: urllib3<1.25,>=1.15 in /opt/conda/lib/python3.6/site-packages (from kfp) (1.24.1)\n",
      "Requirement already satisfied, skipping upgrade: jsonschema>=3.0.1 in /opt/conda/lib/python3.6/site-packages (from kfp) (3.0.1)\n",
      "Requirement already satisfied, skipping upgrade: click==7.0 in /opt/conda/lib/python3.6/site-packages (from kfp) (7.0)\n",
      "Requirement already satisfied, skipping upgrade: certifi in /opt/conda/lib/python3.6/site-packages (from kfp) (2019.3.9)\n",
      "Requirement already satisfied, skipping upgrade: google-cloud-storage>=1.13.0 in /opt/conda/lib/python3.6/site-packages (from kfp) (1.18.0)\n",
      "Requirement already satisfied, skipping upgrade: asn1crypto>=0.21.0 in /opt/conda/lib/python3.6/site-packages (from cryptography>=2.4.2->kfp) (0.24.0)\n",
      "Requirement already satisfied, skipping upgrade: cffi!=1.11.3,>=1.8 in /opt/conda/lib/python3.6/site-packages (from cryptography>=2.4.2->kfp) (1.12.2)\n",
      "Requirement already satisfied, skipping upgrade: cachetools>=2.0.0 in /opt/conda/lib/python3.6/site-packages (from google-auth>=1.6.1->kfp) (3.1.0)\n",
      "Requirement already satisfied, skipping upgrade: rsa>=3.1.4 in /opt/conda/lib/python3.6/site-packages (from google-auth>=1.6.1->kfp) (4.0)\n",
      "Requirement already satisfied, skipping upgrade: pyasn1-modules>=0.2.1 in /opt/conda/lib/python3.6/site-packages (from google-auth>=1.6.1->kfp) (0.2.4)\n",
      "Requirement already satisfied, skipping upgrade: requests<3.0.0,>=2.0.1 in /opt/conda/lib/python3.6/site-packages (from requests-toolbelt>=0.8.0->kfp) (2.21.0)\n",
      "Requirement already satisfied, skipping upgrade: wrapt<2,>=1.10 in /opt/conda/lib/python3.6/site-packages (from Deprecated->kfp) (1.11.2)\n",
      "Requirement already satisfied, skipping upgrade: setuptools>=21.0.0 in /opt/conda/lib/python3.6/site-packages (from kubernetes<=9.0.0,>=8.0.0->kfp) (40.9.0)\n",
      "Requirement already satisfied, skipping upgrade: requests-oauthlib in /opt/conda/lib/python3.6/site-packages (from kubernetes<=9.0.0,>=8.0.0->kfp) (1.2.0)\n",
      "Requirement already satisfied, skipping upgrade: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /opt/conda/lib/python3.6/site-packages (from kubernetes<=9.0.0,>=8.0.0->kfp) (0.56.0)\n",
      "Requirement already satisfied, skipping upgrade: attrs>=17.4.0 in /opt/conda/lib/python3.6/site-packages (from jsonschema>=3.0.1->kfp) (19.1.0)\n",
      "Requirement already satisfied, skipping upgrade: pyrsistent>=0.14.0 in /opt/conda/lib/python3.6/site-packages (from jsonschema>=3.0.1->kfp) (0.14.11)\n",
      "Requirement already satisfied, skipping upgrade: google-resumable-media>=0.3.1 in /opt/conda/lib/python3.6/site-packages (from google-cloud-storage>=1.13.0->kfp) (0.3.2)\n",
      "Requirement already satisfied, skipping upgrade: google-cloud-core<2.0dev,>=1.0.0 in /opt/conda/lib/python3.6/site-packages (from google-cloud-storage>=1.13.0->kfp) (1.0.3)\n",
      "Requirement already satisfied, skipping upgrade: pycparser in /opt/conda/lib/python3.6/site-packages (from cffi!=1.11.3,>=1.8->cryptography>=2.4.2->kfp) (2.19)\n",
      "Requirement already satisfied, skipping upgrade: pyasn1>=0.1.3 in /opt/conda/lib/python3.6/site-packages (from rsa>=3.1.4->google-auth>=1.6.1->kfp) (0.4.5)\n",
      "Requirement already satisfied, skipping upgrade: chardet<3.1.0,>=3.0.2 in /opt/conda/lib/python3.6/site-packages (from requests<3.0.0,>=2.0.1->requests-toolbelt>=0.8.0->kfp) (3.0.4)\n",
      "Requirement already satisfied, skipping upgrade: idna<2.9,>=2.5 in /opt/conda/lib/python3.6/site-packages (from requests<3.0.0,>=2.0.1->requests-toolbelt>=0.8.0->kfp) (2.8)\n",
      "Requirement already satisfied, skipping upgrade: oauthlib>=3.0.0 in /opt/conda/lib/python3.6/site-packages (from requests-oauthlib->kubernetes<=9.0.0,>=8.0.0->kfp) (3.0.1)\n",
      "Requirement already satisfied, skipping upgrade: google-api-core<2.0.0dev,>=1.14.0 in /opt/conda/lib/python3.6/site-packages (from google-cloud-core<2.0dev,>=1.0.0->google-cloud-storage>=1.13.0->kfp) (1.14.2)\n",
      "Requirement already satisfied, skipping upgrade: pytz in /opt/conda/lib/python3.6/site-packages (from google-api-core<2.0.0dev,>=1.14.0->google-cloud-core<2.0dev,>=1.0.0->google-cloud-storage>=1.13.0->kfp) (2018.9)\n",
      "Requirement already satisfied, skipping upgrade: protobuf>=3.4.0 in /opt/conda/lib/python3.6/site-packages (from google-api-core<2.0.0dev,>=1.14.0->google-cloud-core<2.0dev,>=1.0.0->google-cloud-storage>=1.13.0->kfp) (3.7.1)\n",
      "Requirement already satisfied, skipping upgrade: googleapis-common-protos<2.0dev,>=1.6.0 in /opt/conda/lib/python3.6/site-packages (from google-api-core<2.0.0dev,>=1.14.0->google-cloud-core<2.0dev,>=1.0.0->google-cloud-storage>=1.13.0->kfp) (1.6.0)\n",
      "\u001b[33mYou are using pip version 19.0.1, however version 19.2.3 is available.\n",
      "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n",
      "Collecting tensorflow==1.14\n",
      "\u001b[?25l  Downloading https://files.pythonhosted.org/packages/de/f0/96fb2e0412ae9692dbf400e5b04432885f677ad6241c088ccc5fe7724d69/tensorflow-1.14.0-cp36-cp36m-manylinux1_x86_64.whl (109.2MB)\n",
      "\u001b[K    100% |████████████████████████████████| 109.2MB 279kB/s eta 0:00:01\n",
      "\u001b[?25hCollecting tensorboard<1.15.0,>=1.14.0 (from tensorflow==1.14)\n",
      "\u001b[?25l  Downloading https://files.pythonhosted.org/packages/91/2d/2ed263449a078cd9c8a9ba50ebd50123adf1f8cfbea1492f9084169b89d9/tensorboard-1.14.0-py3-none-any.whl (3.1MB)\n",
      "\u001b[K    100% |████████████████████████████████| 3.2MB 7.2MB/s eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied, skipping upgrade: keras-applications>=1.0.6 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.0.7)\n",
      "Requirement already satisfied, skipping upgrade: numpy<2.0,>=1.14.5 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.16.2)\n",
      "Requirement already satisfied, skipping upgrade: absl-py>=0.7.0 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (0.7.1)\n",
      "Requirement already satisfied, skipping upgrade: grpcio>=1.8.6 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.19.0)\n",
      "Collecting google-pasta>=0.1.6 (from tensorflow==1.14)\n",
      "\u001b[?25l  Downloading https://files.pythonhosted.org/packages/d0/33/376510eb8d6246f3c30545f416b2263eee461e40940c2a4413c711bdf62d/google_pasta-0.1.7-py3-none-any.whl (52kB)\n",
      "\u001b[K    100% |████████████████████████████████| 61kB 25.5MB/s ta 0:00:01\n",
      "\u001b[?25hCollecting tensorflow-estimator<1.15.0rc0,>=1.14.0rc0 (from tensorflow==1.14)\n",
      "\u001b[?25l  Downloading https://files.pythonhosted.org/packages/3c/d5/21860a5b11caf0678fbc8319341b0ae21a07156911132e0e71bffed0510d/tensorflow_estimator-1.14.0-py2.py3-none-any.whl (488kB)\n",
      "\u001b[K    100% |████████████████████████████████| 491kB 25.5MB/s ta 0:00:01\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[?25hRequirement already satisfied, skipping upgrade: keras-preprocessing>=1.0.5 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.0.9)\n",
      "Requirement already satisfied, skipping upgrade: six>=1.10.0 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.12.0)\n",
      "Requirement already satisfied, skipping upgrade: gast>=0.2.0 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (0.2.2)\n",
      "Requirement already satisfied, skipping upgrade: protobuf>=3.6.1 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (3.7.1)\n",
      "Requirement already satisfied, skipping upgrade: wrapt>=1.11.1 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.11.2)\n",
      "Requirement already satisfied, skipping upgrade: termcolor>=1.1.0 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (1.1.0)\n",
      "Requirement already satisfied, skipping upgrade: wheel>=0.26 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (0.33.1)\n",
      "Requirement already satisfied, skipping upgrade: astor>=0.6.0 in /opt/conda/lib/python3.6/site-packages (from tensorflow==1.14) (0.7.1)\n",
      "Collecting setuptools>=41.0.0 (from tensorboard<1.15.0,>=1.14.0->tensorflow==1.14)\n",
      "\u001b[?25l  Downloading https://files.pythonhosted.org/packages/b2/86/095d2f7829badc207c893dd4ac767e871f6cd547145df797ea26baea4e2e/setuptools-41.2.0-py2.py3-none-any.whl (576kB)\n",
      "\u001b[K    100% |████████████████████████████████| 583kB 23.7MB/s ta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied, skipping upgrade: markdown>=2.6.8 in /opt/conda/lib/python3.6/site-packages (from tensorboard<1.15.0,>=1.14.0->tensorflow==1.14) (3.1)\n",
      "Requirement already satisfied, skipping upgrade: werkzeug>=0.11.15 in /opt/conda/lib/python3.6/site-packages (from tensorboard<1.15.0,>=1.14.0->tensorflow==1.14) (0.15.2)\n",
      "Requirement already satisfied, skipping upgrade: h5py in /opt/conda/lib/python3.6/site-packages (from keras-applications>=1.0.6->tensorflow==1.14) (2.9.0)\n",
      "\u001b[31mfairing 0.5 has requirement oauth2client>=4.0.0, but you'll have oauth2client 3.0.0 which is incompatible.\u001b[0m\n",
      "Installing collected packages: setuptools, tensorboard, google-pasta, tensorflow-estimator, tensorflow\n",
      "  Found existing installation: setuptools 40.9.0\n",
      "    Uninstalling setuptools-40.9.0:\n",
      "      Successfully uninstalled setuptools-40.9.0\n",
      "  Found existing installation: tensorboard 1.13.1\n",
      "    Uninstalling tensorboard-1.13.1:\n",
      "      Successfully uninstalled tensorboard-1.13.1\n",
      "  Found existing installation: tensorflow-estimator 1.13.0\n",
      "    Uninstalling tensorflow-estimator-1.13.0:\n",
      "      Successfully uninstalled tensorflow-estimator-1.13.0\n",
      "  Found existing installation: tensorflow 1.13.1\n",
      "    Uninstalling tensorflow-1.13.1:\n",
      "      Successfully uninstalled tensorflow-1.13.1\n",
      "Successfully installed google-pasta-0.1.7 setuptools-41.2.0 tensorboard-1.14.0 tensorflow-1.14.0 tensorflow-estimator-1.14.0\n",
      "\u001b[33mYou are using pip version 19.0.1, however version 19.2.3 is available.\n",
      "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "# Install Pipeline SDK - This only needs to be ran once in the enviroment. \n",
    "!python3 -m pip install 'kfp>=0.1.31' --quiet\n",
    "!pip3 install tensorflow==1.14 --upgrade"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## KubeFlow Pipelines Serving Component\n",
    "In this notebook, we will demo:\n",
    "\n",
    "* Saving a Keras model in a format compatible with TF Serving\n",
    "* Creating a pipeline to serve a trained model within a KubeFlow cluster\n",
    "\n",
    "Reference documentation:\n",
    "* https://www.tensorflow.org/tfx/serving/architecture\n",
    "* https://www.tensorflow.org/beta/guide/keras/saving_and_serializing\n",
    "* https://www.kubeflow.org/docs/components/serving/tfserving_new/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setup\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "# Set your output and project. !!!Must Do before you can proceed!!!\n",
    "project =  'Your-Gcp-Project-ID'                          #'Your-GCP-Project-ID'\n",
    "model_name = 'model-name'                                      # Model name matching TF_serve naming requirements       \n",
    "import time\n",
    "ts = int(time.time())\n",
    "model_version = str(ts)                                            # Here we use timestamp as version to avoid conflict \n",
    "output = 'Your-Gcs-Path'     # A GCS bucket for asset outputs\n",
    "KUBEFLOW_DEPLOYER_IMAGE = 'gcr.io/ml-pipeline/ml-pipeline-kubeflow-deployer:1.7.0-rc.2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_path = '%s/%s' % (output,model_name)               \n",
    "model_version_path = '%s/%s/%s' % (output,model_name,model_version)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load a Keras Model \n",
    "Loading a pretrained Keras model to use as an example. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Exception ignored in: <bound method _CheckpointRestoreCoordinator.__del__ of <tensorflow.python.training.tracking.util._CheckpointRestoreCoordinator object at 0x7f31dc197f98>>\n",
      "Traceback (most recent call last):\n",
      "  File \"/opt/conda/lib/python3.6/site-packages/tensorflow/python/training/tracking/util.py\", line 244, in __del__\n",
      "    .format(pretty_printer.node_names[node_id]))\n",
      "  File \"/opt/conda/lib/python3.6/site-packages/tensorflow/python/training/tracking/util.py\", line 93, in node_names\n",
      "    path_to_root[node_id] + (child.local_name,))\n",
      "  File \"/opt/conda/lib/python3.6/site-packages/tensorflow/python/training/tracking/object_identity.py\", line 76, in __getitem__\n",
      "    return self._storage[self._wrap_key(key)]\n",
      "KeyError: (<tensorflow.python.training.tracking.object_identity._ObjectIdentityWrapper object at 0x7f32aca065f8>,)\n"
     ]
    }
   ],
   "source": [
    "model = tf.keras.applications.NASNetMobile(input_shape=None,\n",
    "                                   include_top=True,\n",
    "                                   weights='imagenet',\n",
    "                                   input_tensor=None,\n",
    "                                   pooling=None,\n",
    "                                   classes=1000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Saved the Model for TF-Serve\n",
    "Save the model using keras export_saved_model function. Note that specifically for TF-Serve the output directory should be structure as model_name/model_version/saved_model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.keras.experimental.export_saved_model(model, model_version_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a pipeline using KFP TF-Serve component\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "def kubeflow_deploy_op():\n",
    "    return dsl.ContainerOp(\n",
    "        name = 'deploy',\n",
    "        image = KUBEFLOW_DEPLOYER_IMAGE,\n",
    "        arguments = [\n",
    "            '--model-export-path', model_path,\n",
    "            '--server-name', model_name,\n",
    "        ]\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "import kfp\n",
    "import kfp.dsl as dsl\n",
    "\n",
    "# The pipeline definition\n",
    "@dsl.pipeline(\n",
    "    name='sample-model-deployer',\n",
    "    description='Sample for deploying models using KFP model serving component'\n",
    ")\n",
    "def model_server():\n",
    "    deploy = kubeflow_deploy_op()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Submit pipeline for execution on Kubeflow Pipelines cluster"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "Run link <a href=\"/pipeline/#/runs/details/97fe4bb2-da45-11e9-a9f0-42010a8001f3\" target=\"_blank\" >here</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "kfp.Client().create_run_from_pipeline_func(model_server, arguments={})\n",
    "\n",
    "#vvvvvvvvv This link leads to the run information page. (Note: There is a bug in JupyterLab that modifies the URL and makes the link stop working)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}